using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Threading;
using DotNet.Utilities;
using DotNet.Utilities.ConsoleHelper;
using WPFTemplateLib.Controls.WpfToast;
using WPFTemplateLib.WpfHelpers;

namespace WPFTemplateLib.Mvvm
{
	/// <summary>
	/// ViewModel 基类
	/// </summary>
	public class ViewModelBase : NotifyDataErrorObject
	{
		#region 成员构造

		public Dispatcher Dispatcher => Application.Current?.Dispatcher;

		/// <summary>
		/// 是否设置控制台输出重定向
		/// </summary>
		protected virtual bool IsSetConsoleOutput { get; set; }

		public ViewModelBase()
		{
			if(IsSetConsoleOutput)
			{ 
				SetConsoleOutput();
			}

			_ = InitAsync();
		}

		/// <summary>
		/// 设置 Console 输出（默认调用 ShowInfoUc 方法）
		/// </summary>
		/// <remarks>
		///	需设置 <see cref="IsSetConsoleOutput"/> 为 true 才有效
		/// </remarks>
		protected virtual void SetConsoleOutput()
		{
			Console.SetOut(new ConsoleWriter(s =>
			{
				ShowInfoUc(s);
			}));
		}

		/// <summary>
		/// 异步初始化
		/// </summary>
		private async Task InitAsync()
		{
			await Task.Yield();

			InitDataAfterConstruction();
		}

		/// <summary>
		/// 初始化数据（在构造之后）
		/// </summary>
		protected virtual void InitDataAfterConstruction() { } 

		#endregion

		#region 程序运行消息输出

		private string _Info;
		/// <summary>
		/// 信息窗内容;
		/// </summary>
		public string Info
		{
			get => _Info;
			set => SetPropertyWithoutCompare(ref _Info, value);
		}

		/// <summary>
		/// 输出信息方法（界面需绑定 Info）
		/// </summary>
		/// <param name="info">信息内容</param>
		/// <param name="isAddBlankLine">是否空行</param>
		public void ShowInfo(string info, bool isAddBlankLine = false)
		{
			string tail = Environment.NewLine.Repeat(isAddBlankLine ? 2 : 1);
			Info += $"[{DateTime.Now:HH:mm:ss.ffff}] {info}{tail}";
		}

		/// <summary>
		/// 使用信息区用户控件时使用的输出信息方法 <para/>
		/// （界面需使用 UC_InfoRegion 并将 InfoText 绑定 Info，或者直接使用 PanelWithMessage 控件）
		/// </summary>
		/// <param name="info">信息内容</param>
		/// <param name="isAddBlankLine">是否空行</param>
		public void ShowInfoUc(string info, bool isAddBlankLine = false)
		{
			string tail = Environment.NewLine.Repeat(isAddBlankLine ? 1 : 0);
			Dispatcher.Invoke(() =>
			{
				Info = $"{info}{tail}";
				OnPropertyChanged(nameof(Info)); //如果值相同，set 方法不会进入，所以此处手动调用变动通知;
			});
		}

		#endregion

		#region 气泡弹窗命令

		private RelayCommand<List<object>> _ToastToWindowCmd;
		/// <summary>
		/// 气泡弹窗(到窗体)命令
		/// </summary>
		/// <example>
		/// <code>
		/// //Xaml中调用：
		/// &lt;Button Content="测试通过命令调用（窗体中间）" Command="{Binding ToastToWindowCmd}">
		///      &lt;Button.CommandParameter>
		///          &lt;MultiBinding Converter="{StaticResource MultiToListObjectConverter}">
		///              &lt;Binding Path="Content" RelativeSource="{RelativeSource Self}"/>
		///              &lt;Binding RelativeSource="{RelativeSource AncestorType=Window}"/>
		///          &lt;/MultiBinding>
		///      &lt;/Button.CommandParameter>
		///  &lt;/Button>
		/// </code>
		/// <code>
		/// //代码中调用：
		/// ToastToWindowCmd.Execute(new List&lt;object&gt; { "请先选择一项" });
		/// </code>
		/// </example>
		public RelayCommand<List<object>> ToastToWindowCmd => _ToastToWindowCmd ??= new RelayCommand<List<object>>(paras =>
		{
			Task.Run(delegate
			{
				string msg = string.Empty;
				if(paras.Any())
				{
					msg = paras[0] + "";
				}

				Window win = null;
				if(paras.Count >= 2)
				{
					win = paras[1] as Window;
				}

				Dispatcher.Invoke(delegate
				{
					Toast.Show(win, msg, new ToastOptions()
					{
						Icon = ToastIcons.Information,
						Location = ToastLocation.OwnerCenter,
						Time = 5000,
					});
				});
			});
		});

		private RelayCommand<string> _ToastToScreenCmd;
		/// <summary>
		/// 气泡弹窗(到屏幕)命令
		/// </summary>
		/// <example>
		/// <code>
		/// //Xaml中调用：
		/// &lt;Button Content="测试通过命令调用（屏幕中间）" Command="{Binding ToastToScreenCmd}" CommandParameter="{Binding Content, RelativeSource={RelativeSource Self}}"/&gt;
		/// </code>
		/// <code>
		/// //代码中调用：
		/// ToastToScreenCmd.Execute("请先选择一项");
		/// </code>
		/// </example>
		public RelayCommand<string> ToastToScreenCmd => _ToastToScreenCmd ??= new RelayCommand<string>(msg =>
		{
			ToastToScreen(msg);
		});

		/// <summary>
		/// 气泡弹窗到屏幕中间的方法
		/// </summary>
		/// <param name="msg">消息内容</param>
		/// <param name="toastIcon">图标</param>
		/// <param name="millisecond">显示持续时间（毫秒）</param>
		/// <param name="iconForeground">图标颜色</param>
		public void ToastToScreen(string msg, ToastIcons toastIcon = ToastIcons.Information, int millisecond = 5000, Brush iconForeground = null)
		{
			if(string.IsNullOrWhiteSpace(msg))
			{
				return;
			}

			Task.Run(delegate
			{
				Dispatcher.Invoke(delegate
				{
					var option = new ToastOptions()
					{
						Icon = toastIcon,
						Location = ToastLocation.ScreenCenter,
						Time = millisecond,
						//ToastWidth = Double.NaN﻿,
						TextWidth = 380,
					};

					if(iconForeground != null)
					{
						option.IconForeground = iconForeground;
					}

					Toast.Show(msg, option);
				});
			});
		}

		/// <summary>
		/// 气泡弹窗到屏幕中间（警告消息）
		/// </summary>
		/// <param name="msg">消息</param>
		/// <param name="millisecond">持续时间（毫秒）</param>
		public void ToastToScreenWarning(string msg, int millisecond = 5000)
		{
			ToastToScreen(msg, ToastIcons.Warning, millisecond, new SolidColorBrush(Colors.IndianRed));
		}

		/// <summary>
		/// 气泡弹窗到屏幕中间（错误消息）
		/// </summary>
		/// <param name="msg">消息</param>
		/// <param name="millisecond">持续时间（毫秒）</param>
		public void ToastToScreenError(string msg, int millisecond = 5000)
		{
			ToastToScreen(msg, ToastIcons.Error, millisecond, new SolidColorBrush(Colors.OrangeRed));
		}

		/// <summary>
		/// 气泡弹窗到窗口中间的方法
		/// </summary>
		/// <param name="msg">消息内容</param>
		/// <param name="win">窗口(不传则自动判断)</param>
		/// <param name="toastIcon">图标</param>
		/// <param name="millisecond">显示持续时间（毫秒）</param>
		/// <param name="iconForeground">图标颜色</param>
		public void ToastToWindow(string msg, Window win = null, ToastIcons toastIcon = ToastIcons.Information, int millisecond = 5000, Brush iconForeground = null)
		{
			if(string.IsNullOrWhiteSpace(msg))
			{
				return;
			}

			Task.Run(delegate
			{
				Dispatcher.Invoke(delegate
				{
					var option = new ToastOptions()
					{
						Icon = toastIcon,
						Location = ToastLocation.OwnerCenter,
						Time = millisecond,
						//ToastWidth = Double.NaN﻿,
						//方法一：
						TextWidth = 380,
						//方法二：
						//ToastMaxWidth = 420﻿,
					};

					if(iconForeground != null)
					{
						option.IconForeground = iconForeground;
					}

					Toast.Show(win, msg, option);
				});
			});
		}

		#endregion

		#region 处理载入事件

		/// <summary>
		/// 是否是第一次载入
		/// </summary>
		private bool _isFirstLoad = true;

		#region [命令] 载入
		private ICommand _LoadedCmd;
		public ICommand LoadedCmd => _LoadedCmd ??= new RelayCommand<object>(ExecuteLoadedCmd);
		private void ExecuteLoadedCmd(object para)
		{
			OnLoaded(_isFirstLoad);

			if(_isFirstLoad)
			{
				_isFirstLoad = false;
			}
		}
		#endregion

		/// <summary>
		/// 载入完成时的处理方法
		/// </summary>
		/// <remarks>
		/// 需在 Loaded 事件时调用 <see cref="LoadedCmd"/> 命令，此方法才有效。
		/// </remarks>
		/// <param name="isFirst">是否是第一次载入</param>
		protected virtual void OnLoaded(bool isFirst) { }

		#endregion
	}
}
